package com.amadeus.spin.devtool.soap.shooter;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;

import com.amadeus.spin.devtool.soap.shooter.config.ConfigurationParameters;
import com.amadeus.spin.devtool.soap.shooter.process.ReplaceInputVersion;

public class CommandLineOptions {
  private static final String EQUAL_STR = "=";

  private static final String VERSION = "version";

  private static Logger log = Logger.getLogger(CommandLineOptions.class);

  /**
   * The available options for the soapshooter.
   */
  protected static Options options = new Options();

  /**
   * Simple options - subset of Options, only used for display purposes. Later
   * on we can get rid of the m_options and all the 'advanced' (unused) options
   */
  private static Options simpleOptions = new Options();

  /**
   * The parameters recognized in input
   */
  private CommandLine m_cmdline;

  private Option help = new Option("h", "help", false, "Description of Standard options.");

  private Option advancedhelp = new Option("hh", "hhelp", false, "Description of deprecated options.");

  private Option replace = new Option("re", "replace", true,
      "Replace tag values in the input. Example: --replace SITE_PARAMETER=VALUE1 MAX_PRICE=VALUE2");

  private Option replaceFilters = new Option(
      "rf",
      "replaceFilters",
      true,
      "Replace filter values definitions in the scenario. Example: " +
          "--replaceFilters name=FILTER_NAME|type=string|value=NEW_VALUE name=FILTER_NAME_2|type=date|value+=1MONTH");

  private Option skipLogFile = new Option("n", "no-logfile", false,
      "Disables the creation of log file containing the soapshooter traces.");

  private Option sessionLess = new Option("sl", "sessionless", false,
      "Disables the Cookie forwarding. Used for Session less applications");

  private Option forceOverride = new Option(
      "fo",
      "force-override",
      true,
      "Forces the override of parameters. Syntax is the same as replace. Example: -fo version=xx.x site=XDEFXDEF " +
          "lang=GB SITE_PARAMETER1=VALUE1 SITE_PARAMETER2=VALUE2");

  private Option file = new Option("f", "file", true, "Soap scenario file to process.");

  private Option paramsFile = new Option("p", "paramsfile", true,
      "File that defines a list of parameters that can be used within if / else statements");

  private Option wlsVersion = new Option("wls", "wlsVersion", true,
      "The logging directory of the local server shooted; one of ('old', 'Vxy.z'); defaults to 'old'.");

  private Option verbose = new Option("v", "verbose", false, "Verbose output - details the processing.");

  private Option zip = new Option("z", "zip", false, "Zips the soapshooter log and result files in output directory.");

  private Option server = new Option("s", "server", true, "Specify the target server and port.");

  private Option outputdir = new Option("o", "out", true, "Specify the target output directory.");

  private Option htpath = new Option(
      "ht",
      "htpath",
      true,
      "Specify the path to hosttraces. They will be copied into your soap output directory. " +
          "You must the rights to write on the hosttrace directory.");

  private Option router = new Option("r", "router", true,
      "Specify the target RPC router (defaults to default is set to /SOAPRPCRouterServlet.");

  private Option proxy = new Option("p", "proxy", true, "Activate proxy feature and set proxy host and port.");

  private Option proxyusername = new Option("pu", "proxyusername", true,
      "username for HTTP proxy Basic authentication.");

  private Option proxypassword = new Option("pp", "proxypassword", true,
      "password for HTTP proxy Basic authentication.");

  private Option sessionid = new Option("ss", "sessionid", true,
      "session id to put in request to carry on a existing server side session.");

  private Option urlRewriting = new Option("u", "urlRewriting", false,
      "activate URL rewriting (false by default and only available with apache client.");

  private Option urlParams = new Option("urlp", "urlparams", true,
      "Add additional parameters to the url. Syntax of the parameter is var1=val1&val2=var2&var3=val3");

  /**
   * Janus Id has come back so a dedicated parameter is used to help appending the JanusId to the input requests.
   */
  private Option janusIdParam = new Option("jid", "janusid", true,
      "Append the Janus Id to all your input request.");

  private Option ssl = new Option("ssl", "ssl", false, "Dynamic registration of SSL provider and HTTPS support.");

  private Option timeout = new Option("t", "timeout", true,
      "Specify SOAP requests timeout in ms, default is 0 for no timeout.");

  /**
   * TODO [EAI 20110225] SoapShooter schema validation : update comments
   * concerning precedence as soon as platform schema are used on every
   * platform
   */
  private Option useServerSchema = new Option("uss", "useserverschema", false,
      "Use schema from target server (-uos will take precedence over -uss if options are both present).");

  private Option useOriginalSchema = new Option(
      "uos",
      "useoriginalschema",
      false,
      "Use original schema specified in soap request  (-uos will take precedence over -uss " +
          "if options are both present).");

  // The specific version for the xsd schemes that overrides the hard coded
  // ones in the xml files.
  private Option useCustomVersion = new Option("ver", "usecustomversion", true,
      "Specify schema versions of the xsd files. Format : [0-99].[0-99] or [0-99].[0-99].[0-9]");

  public CommandLineOptions() {
    createCommandLineOptions();
  }

  public boolean parse(String[] args) {
    boolean isSuccess = true;
    m_cmdline = null;
    CommandLineParser parser = new GnuParser();
    try {
      m_cmdline = parser.parse(options, args);
    }
    catch (ParseException e) {
      log.error("Error in command line : " + e.getMessage());
      displayUsage();
      isSuccess = false;
    }
    return isSuccess;
  }

  /**
   * Sets some parameters according to the options
   *
   * @return
   * @throws FatalException
   */
  public boolean setupEnvironment() throws FatalException {
    if (m_cmdline.hasOption(help.getLongOpt())) {
      displayUsage();
      return false;
    }
    if (m_cmdline.hasOption(advancedhelp.getLongOpt())) {
      displayAdvancedUsage();
      return false;
    }
    if (m_cmdline.hasOption(file.getLongOpt())) {
      String result = m_cmdline.getOptionValue(file.getLongOpt());
      ConfigurationParameters.setScenarioFileName(result);
    }
    if (m_cmdline.hasOption(paramsFile.getOpt())) {
      String result = m_cmdline.getOptionValue(paramsFile.getOpt());
      ConfigurationParameters.setParamsFileName(result);
    }
    if (m_cmdline.hasOption(htpath.getLongOpt())) {
      String result = m_cmdline.getOptionValue(htpath.getLongOpt());
      ConfigurationParameters.setHosttracesPath(result);
    }
    if (m_cmdline.hasOption(proxy.getLongOpt())) {
      processProxyOption();
    }
    if (m_cmdline.hasOption(proxypassword.getLongOpt())) {
      String result = m_cmdline.getOptionValue(proxypassword.getLongOpt());
      ConfigurationParameters.setProxyPassword(result);
    }
    if (m_cmdline.hasOption(proxyusername.getLongOpt())) {
      String result = m_cmdline.getOptionValue(proxyusername.getLongOpt());
      ConfigurationParameters.setProxyUserName(result);
    }
    if (m_cmdline.hasOption(router.getLongOpt())) {
      String result = m_cmdline.getOptionValue(router.getLongOpt());
      ConfigurationParameters.setRouter(result);
    }
    if (m_cmdline.hasOption(replace.getLongOpt())) {
      String replaces[] = m_cmdline.getOptionValues(replace.getLongOpt());
      setReplaceOption(replaces);
    }
    if (m_cmdline.hasOption(replaceFilters.getLongOpt())) {
      String replaces[] = m_cmdline.getOptionValues(replaceFilters.getLongOpt());
      ConfigurationParameters.setOptionFilters(replaces);
    }
    if (m_cmdline.hasOption(forceOverride.getLongOpt())) {
      String replaces[] = m_cmdline.getOptionValues(forceOverride.getLongOpt());
      setForceOverrideOption(replaces);
    }
    if (m_cmdline.hasOption(sessionid.getLongOpt())) {
      String result = m_cmdline.getOptionValue(sessionid.getLongOpt());
      ConfigurationParameters.setSessionId(result);
    }
    if (m_cmdline.hasOption(server.getLongOpt())) {
      String result = m_cmdline.getOptionValue(server.getLongOpt());
      ConfigurationParameters.setServer(result);
    }
    if (m_cmdline.hasOption(skipLogFile.getLongOpt())) {
      ConfigurationParameters.setDumpSoapTrace(false);
    }
    if (m_cmdline.hasOption(sessionLess.getLongOpt())) {
      ConfigurationParameters.setSessionLess(true);
    }
    if (m_cmdline.hasOption(ssl.getLongOpt())) {
      processSSLOption();
    }
    if (m_cmdline.hasOption(timeout.getLongOpt())) {
      processTimeoutOption();
    }
    if (m_cmdline.hasOption(urlRewriting.getLongOpt())) {
      ConfigurationParameters.setURLRewriting(true);
    }
    if (m_cmdline.hasOption(verbose.getLongOpt())) {
      ConfigurationParameters.setVerbose(true);
    }
    if (m_cmdline.hasOption(wlsVersion.getLongOpt())) {
      String result = m_cmdline.getOptionValue(wlsVersion.getLongOpt());
      if (Pattern.matches(ReplaceInputVersion.VERSION_PATTERN, result)) {
        ConfigurationParameters.setServerVersionUsed(result);
      }
    }
    if (m_cmdline.hasOption(zip.getLongOpt())) {
      ConfigurationParameters.setZip(true);
    }

    if (m_cmdline.hasOption(outputdir.getLongOpt())) {
      String outputDir = m_cmdline.getOptionValue(outputdir.getLongOpt());
      ConfigurationParameters.setOverridenOutputDir(outputDir);
    }

    /**
     * TODO [EAI 20110225] SoapShooter schema validation : Invert behaviour as
     * soon as platform schema are used on every platform
     */
    boolean useTargetServerSchema = m_cmdline.hasOption(useServerSchema.getLongOpt());
    if (useTargetServerSchema) {
      ConfigurationParameters.setUseTargetServerSchema();
    }

    if (!useTargetServerSchema || m_cmdline.hasOption(useOriginalSchema.getLongOpt())) {
      ConfigurationParameters.setUseOriginalServerSchema();
    }

    // Override Schema version
    if (m_cmdline.hasOption(useCustomVersion.getLongOpt())) {
      processSchemaVersionOption();
    }
    if (m_cmdline.hasOption(urlParams.getLongOpt())) {
      processUrlParamsOption();
    }
    if (m_cmdline.hasOption(janusIdParam.getLongOpt())) {
      processJanusIdParamsOption();
    }

    return true;
  }

  /**
   * Sets the additional url params if it exists and is valid (contains '=' and not null).
   *
   * @throws FatalException
   */
  private void processUrlParamsOption() throws FatalException {
    String additionalParams = m_cmdline.getOptionValue(urlParams.getOpt());
    if (additionalParams == null || !additionalParams.contains(EQUAL_STR)) {
      throw new FatalException("Error: urlp option value should not be null and of the format var1=val1&var2=val2.");
    }
    ConfigurationParameters.setUrlAdditionalParams(additionalParams);
  }

  /**
   * Sets the JanusId of the scenario, checks if it is valid
   *
   * @throws FatalException
   */
  private void processJanusIdParamsOption() throws FatalException {
    String janusId = m_cmdline.getOptionValue(janusIdParam.getOpt());
    if (janusId == null || janusId.length() == 0) {
      throw new FatalException("Error: janusId option requires a parameter.");
    }
    ConfigurationParameters.setJanusId(janusId);
  }

  /**
   * Sets the schema version if it is valid
   */
  private void processSchemaVersionOption() {
    String version = m_cmdline.getOptionValue(useCustomVersion.getOpt());
    // Check if the version has a correct format : Numbers and points '.'
    if (Pattern.matches(ReplaceInputVersion.VERSION_PATTERN, version)) {
      ConfigurationParameters.setCustomSchemaVersion(version);
    }
    else {
      log.warn("Wrong format for overriden version " + version +
          ". It will not be overriden, please refer to help for further details.");
    }
  }

  /**
   * Sets the timeout if has a correct format
   *
   * @throws FatalException
   */
  private void processTimeoutOption() throws FatalException {
    String result = m_cmdline.getOptionValue(timeout.getLongOpt());
    try {
      ConfigurationParameters.setTimeout(Integer.parseInt(result));
    }
    catch (NumberFormatException e) {
      throw new FatalException("Error: Timeout parameter must be an integer.", e);
    }
  }

  /**
   * Sets the SSL property
   */
  private void processSSLOption() {
    try {
      // register dynamically SSL provider
      java.security.Security.addProvider((java.security.Provider)Class.forName(
          "com.sun.net.ssl.internal.ssl.Provider").newInstance());
      // and enable https support
      System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
    }
    catch (InstantiationException e) {
      log.error(e.getMessage());
    }
    catch (IllegalAccessException e) {
      log.error(e.getMessage());
    }
    catch (ClassNotFoundException e) {
      log.error(e.getMessage());
    }
  }

  /**
   * Sets the proxy option
   */
  private void processProxyOption() {
    String result = m_cmdline.getOptionValue(proxy.getLongOpt());
    int pos = result.indexOf(':');
    if (pos != -1) {
      try {
        ConfigurationParameters.setProxyport(Integer.parseInt(result.substring(pos + 1)));
        ConfigurationParameters.setProxyhost(result.substring(0, pos));
      }
      catch (NumberFormatException nfe) {
        log.error(nfe.getMessage());
        ConfigurationParameters.setProxyport(-1);
      }
    }
  }

  /**
   * Analyzes and fills in the parameters from an array of parameters.
   *
   * @return boolean True if the provided parameters are correct and False otherwise
   */
  protected void createCommandLineOptions() {
    replace.setArgs(Option.UNLIMITED_VALUES);
    replace.setValueSeparator(' ');
    replace.setArgName("SITE_PARAMETER=VALUE");
    replaceFilters.setArgs(Option.UNLIMITED_VALUES);
    replaceFilters.setValueSeparator(' ');
    replaceFilters.setArgName("name=FILTER_NAME|type=string|value=NEW_VALUE");
    forceOverride.setArgs(Option.UNLIMITED_VALUES);
    forceOverride.setValueSeparator(' ');
    forceOverride.setArgName("SITE_PARAMETER=VALUE");

    file.setArgName(file.getLongOpt());
    paramsFile.setArgName("params.xml");
    paramsFile.setOptionalArg(true);
    wlsVersion.setArgName(wlsVersion.getLongOpt());
    server.setArgName("http://localhost:7001");
    outputdir.setArgName("dir");
    // Option recordreplay = new Option( "rr", "recordreplay", true,
    // "Specify any input file to be used by the RecordReplay during a replay.
    // Only use if the server is in replay mode."
    // ); recordreplay.setArgName("filename");
    htpath.setArgName("d:\\jdevcc\\[view_dir]\\[path_to_hosttraces]");
    router.setArgName("http://router-url");
    proxy.setArgName("proxyhost:proxyport");
    proxyusername.setArgName("username");
    proxypassword.setArgName("password");
    sessionid.setArgName(sessionid.getLongOpt());
    timeout.setArgName("timeoutvalue");

    // The specific version for the xsd schemes that overrides the hard coded
    // ones in the xml files.
    useCustomVersion.setArgName("14.2");

    Option[] simpleOptionsList = new Option[] { help, advancedhelp, file, paramsFile, server, replace, replaceFilters,
        forceOverride,
        verbose, zip, htpath, outputdir, timeout, router, skipLogFile, sessionLess, useServerSchema, useOriginalSchema,
        useCustomVersion, urlParams };
    for (Option option : simpleOptionsList) {
      simpleOptions.addOption(option);
    }
    Option[] optionsList = new Option[] { help, advancedhelp, file, paramsFile, server, replace, replaceFilters,
        forceOverride,
        verbose, zip, htpath, outputdir, timeout, router, skipLogFile, sessionLess, useServerSchema, useOriginalSchema,
        useCustomVersion, janusIdParam, wlsVersion, proxy, proxyusername, proxypassword, sessionid, urlRewriting, ssl };

    for (Option option : optionsList) {
      options.addOption(option);
    }
  }

  protected void setReplaceOption(String[] toReplace) {
    StringBuilder replaceXmlString = new StringBuilder();
    replaceXmlString.append("<input>");
    for (int i = 0; i < toReplace.length; i++) {
      String[] splitted = toReplace[i].split(EQUAL_STR);
      String replaceKey = splitted[0];
      String replaceValue = splitted[1];
      replaceXmlString.append(String.format("<Replace><entry key=\"%s\" value=\"%s\"/></Replace>", replaceKey,
          replaceValue));
    }
    replaceXmlString.append("</input>");
    ConfigurationParameters.setReplaceXmlString(replaceXmlString.toString());
  }

  // we need to know the target server version, site and language used.
  /**
   * Force the override of given parameters:
   * <ul>
   * <li>call the replace option so that parameters will be replaced if they already exist</li>
   * <li>create an override input.</li>
   * </ul>
   *
   * To create an override input we need a server version (defaults to 13.0), a site and a language.
   *
   * @param toReplace
   */
  protected void setForceOverrideOption(String[] toReplace) {
    setReplaceOption(toReplace);
    Map<String, String> parameters = new HashMap<String, String>();
    for (int i = 0; i < toReplace.length; i++) {
      parameters.put(toReplace[i].split(EQUAL_STR)[0], toReplace[i].split(EQUAL_STR)[1]);
    }
    String version = Constants.DEFAULT_VERSION;
    if (parameters.containsKey(VERSION)) {
      version = parameters.get(VERSION);
    }
    StringBuilder forceOverrideXmlString = new StringBuilder();
    for (Entry<String, String> entry : parameters.entrySet()) {
      String key = entry.getKey();
      if (!VERSION.equals(key)) {
        forceOverrideXmlString.append(String.format("  <%s>%s</%s>\n", key, entry.getValue(), key));
      }
    }
    ConfigurationParameters.setForceOverrideInput(String.format(Constants.DEFAULT_OVERRIDE, version, version,
        forceOverrideXmlString.toString()));
  }

  /**
   * Display command usage.
   *
   * @param options
   *          command options.
   */
  private void displayUsage() {
    displayHelpAndoptions(simpleOptions);
  }

  /**
   * Advanced Display command usage.
   *
   * @param options
   *          command options.
   */
  private void displayAdvancedUsage() {
    displayHelpAndoptions(options);
  }

  /**
   * Display the command line options
   *
   * @param options
   */
  private void displayHelpAndoptions(Options theOptions) {
    HelpFormatter helpFormatter = new HelpFormatter();
    helpFormatter.setWidth(500);
    helpFormatter.printHelp("java -jar [jarname]", "\nOptions of SoapShooter Version " +
        ConfigurationParameters.getSoapShooterVersion(), theOptions,
        "\nFor more information and contact points, please refer to http://topspot/index.php/SoapShooter", true);
  }
}
